package com.silence.gmall.filter;

import com.google.common.collect.Sets;
import com.silence.gmall.enums.TraceIdType;
import com.silence.gmall.holder.TraceIdHolder;
import com.silence.gmall.util.TraceIdUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;

import java.io.IOException;
import java.util.Set;

/**
 * @Author silence
 * @Email 792245022@qq.com
 * @Date 2025-06-11 14:50
 * @Description 链路追踪标识过滤器
 * 负责从request的header里取出TraceId放入TraceIdHolder
 * 优先级放在requestContextFilter之后
 * 若需要在filter获取请求参数和返回值可以参考
 */
@Slf4j
@Order(-100)
@WebFilter(filterName = "traceIdContextFilter", urlPatterns = "/*")
public class TraceIdContextFilter implements Filter {

    /**
     * 链路 ID 的 key
     */
    private static final String TRACE_ID_KEY = "X-Request-Id";

    /**
     * 日志次数前缀的 key
     */
    private static final String LOG_TIMES_PREFIX_KEYS = "X-Log-Prefix";

    /**
     * 初始化参数的 key，排除的 URI key
     */
    private static final String INIT_PARAMETER_KEY = "exclude-uri";

    /**
     * 链路标识
     */
    private static final String TRACE_ID = "traceId";

    /**
     * 默认的排除uri，主要用于权限、日志打印等场合
     */
    private static final String[] EXCLUDE_URI = {"/actuator/health"};

    /**
     * 初始化参数的 key，排除的 URI 列表
     */
    private static Set<String> EXCLUDE_URI_SET;

    /**
     * 初始化
     *
     * @param filterConfig 过滤器配置
     * @throws ServletException servlet异常
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 可以通过配置server.servlet.context-parameters.exclude-uri来覆盖
        String excludeUri = filterConfig.getServletContext().getInitParameter(INIT_PARAMETER_KEY);
        String[] excludeUris;
        if (StringUtils.isNotBlank(excludeUri)) {
            excludeUris = excludeUri.split(",");
        } else {
            excludeUris = EXCLUDE_URI;
        }
        EXCLUDE_URI_SET = Sets.newHashSet(excludeUris);
        log.debug("[traceIdContextFilter]初始化");
    }

    /**
     * 过滤逻辑
     *
     * @param request  请求
     * @param response 响应
     * @param chain    过滤器链
     * @throws IOException      IO 异常
     * @throws ServletException servlet 异常
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 类型转换
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 排除特殊链接
        if (EXCLUDE_URI_SET.contains(httpServletRequest.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }
        // 初始化容器
        TraceIdHolder.init();
        // 从请求头中获取 traceId 和 logTimesPrefix
        String traceId = httpServletRequest.getHeader(TRACE_ID_KEY);
        String logTimesPrefix = httpServletRequest.getHeader(LOG_TIMES_PREFIX_KEYS);
        // 是否需要重新生成链路标识
        boolean need2GenerateTraceId = StringUtils.isBlank(traceId);
        // 获取远程地址，请求路径
        String source = httpServletRequest.getRemoteAddr();
        String target = httpServletRequest.getRequestURI();
        // 判断是否需要生成 traceId
        if (need2GenerateTraceId) {
            traceId = TraceIdUtil.generate(TraceIdType.IN);
            TraceIdHolder.set(traceId);
            log.info("【接口请求开始】 - 来源地址: [{}]，目标地址: [{}]，生成 traceId: [{}]", source, target, traceId);
        } else {
            TraceIdHolder.set(traceId);
            if (StringUtils.isNotBlank(logTimesPrefix)) {
                TraceIdHolder.setLogTimesPrefix(logTimesPrefix);
            }
            log.info("【接口请求开始】 - 来源地址: [{}]，目标地址: [{}]，已有 traceId：[{}]", source, target, traceId);
        }
        // 设置 MDC
        MDC.put(TRACE_ID, traceId);
        chain.doFilter(request, response);
        // 入参和返回值在InterfaceLogAop
        log.info("【接口请求结束】 - 来源地址: [{}]，目标地址: [{}]，traceId：[{}]", source, target, traceId);
        // 清除 MDC
        MDC.clear();
        // 移除容器
        TraceIdHolder.remove();
    }

    /**
     * 销毁方法
     */
    @Override
    public void destroy() {
        log.debug("[traceIdContextFilter]销毁");
    }
}
